/*
 * Copyright (C) 2008-2012  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

#define make_symbol(pointer, name)					\
    enew_entry_base(pointer, name, null, t_symbol, sizeof(esymbol_t))

/*
 * Initialization
 */
evector_t		*type_vector;

static ehash_t		*language_table;
static evector_t	*namespace_vector;

/*
 * Implementation
 */
void
init_symbol(void)
{
    eadd_root((eobject_t *)&type_vector);
    enew_vector((eobject_t *)&type_vector, t_void, 128);

    eadd_root((eobject_t *)&language_table);
    enew_hash((eobject_t *)&language_table, 256);

    eadd_root((eobject_t *)&namespace_vector);
    enew_vector((eobject_t *)&namespace_vector, t_void, 8);
}

void
finish_symbol(void)
{
    erem_root((eobject_t *)&type_vector);
    erem_root((eobject_t *)&language_table);
    erem_root((eobject_t *)&namespace_vector);
}

esymbol_t *
enew_identifier(evector_t *name)
{
    esymbol_t		*symbol;
    eobject_t		*pointer;

    if ((symbol = (esymbol_t *)eget_hash(language_table, name)) == null) {
	gc_ref(pointer);
	make_symbol(pointer, name);
	symbol = *pointer;
	symbol->namespace = (enamespace_t *)language_table;
	eput_hash(language_table, (eentry_t *)symbol);
	gc_dec();
    }

    return (symbol);
}

esymbol_t *
eget_symbol(enamespace_t *namespace, evector_t *name)
{
    if (namespace->fields)
	return ((esymbol_t *)eget_hash(namespace->fields, name));
    return (null);
}

esymbol_t *
eget_bound_symbol(evector_t *name)
{
    esymbol_t		*symbol;
    enamespace_t	*namespace;

    for (namespace = current_namespace;
	 namespace; namespace = namespace->parent) {
	if ((symbol = eget_symbol(namespace, name)))
	    return (symbol);
    }

    return (null);
}

esymbol_t *
enew_symbol(enamespace_t *record, evector_t *name, etag_t *tag)
{
    ebool_t		 gc;
    etype_t		 type;
    eword_t		 length;
    esymbol_t		*symbol;
    eobject_t		*pointer;

#if DEBUG
    /* caller must check with file/line/column context for error messages */
    assert(eget_symbol(record, name) == null);
#endif

    gc_ref(pointer);
    make_symbol(pointer, name);
    symbol = *pointer;
    symbol->tag = tag;
    symbol->namespace = record;
    eput_hash(record->fields, (eentry_t *)symbol);
    gc_dec();

    if (record->vector->offset >= record->vector->length)
	erenew_vector(record->vector, record->vector->length + 4);
    record->vector->v.ptr[record->vector->offset++] = symbol;

    gc = true;
    length = sizeof(eobject_t);
    if (tag) {
	switch (tag->type) {
	    case tag_basic:
		length = ((ebasic_t *)tag->name)->length;
		gc = false;
		break;
	    case tag_auto:	case tag_class:
	    case tag_vector:
		break;
	    default:
		assert(tag->type == tag_function);
		return (symbol);
	}
    }

    type = etype(record);
    assert(type == t_class || type == t_namespace);

    switch (length) {
	case 0:		case 1:
	    break;
	case 2:
	    record->offset = (record->offset + 1) & ~1;
	    break;
	case 3:		case 4:
	    record->offset = (record->offset + 3) & ~3;
	    break;
	default:
	    record->offset = (record->offset + 7) & ~7;
	    break;
    }
    symbol->offset = record->offset;
    record->offset += length;
    if (gc) {
	if (record->gcinfo == null)
	    enew_vector((eobject_t *)&record->gcinfo, t_word, 4);
	else if ((record->gcinfo->offset & 3) == 0)
	    erenew_vector(record->gcinfo, record->gcinfo->offset + 4);
	record->gcinfo->v.w[record->gcinfo->offset++] = symbol->offset;
    }

    return (symbol);
}

ebasic_t *
enew_basic(esymbol_t *name, etype_t type, eint32_t length)
{
    etag_t		*tag;
    ebasic_t		*basic;

    enew(type_vector->v.ptr + type, basic);
    basic = type_vector->v.ptr[type];
    basic->name = name;
    basic->type = type;
    basic->length = length;
    type_vector->offset = type + 1;

    tag = etag_object(basic);

    name->value = basic;
    name->tag = tag;
    name->type = true;

    return (basic);
}

erecord_t *
enew_record(esymbol_t *name)
{
    etag_t		*tag;
    erecord_t		*record;

    if (type_vector->offset >= type_vector->length) {
	erenew_vector(type_vector, type_vector->length + 128);
	erenew_vector(rtti_vector, type_vector->length + 128);
    }
    enew_object(type_vector->v.ptr + type_vector->offset,
		t_class, sizeof(erecord_t));
    record = type_vector->v.ptr[type_vector->offset];
    record->name = name;
    record->type = type_vector->offset;
    ++type_vector->offset;

    enew_hash((eobject_t *)&record->fields, 8);
    record->parent = current_namespace;

    enew_vector((eobject_t *)&record->vector, t_void, 4);

    tag = etag_object(record);

    name->value = record;
    name->tag = tag;
    name->type = true;

    return (record);
}

void
eend_record(erecord_t *record)
{
    ertti_t		*rtti;
    etype_t		 type;

    type = etype(record);
    assert(type == t_class);
    assert(record->length == 0);
#if __WORDSIZE == 32
    record->length = (record->offset + 3) & ~3;
#else
    record->length = (record->offset + 7) & ~7;
#endif

    enew_object(rtti_vector->v.ptr + record->type, t_rtti, sizeof(ertti_t));
    rtti = rtti_vector->v.ptr[record->type];
    if (record->gcinfo) {
	rtti->gcsize = record->gcinfo->offset;
	enew_object((eobject_t *)&rtti->gcinfo, t_word,
		    rtti->gcsize * sizeof(eword_t));
	memcpy(rtti->gcinfo, record->gcinfo->v.w,
	       rtti->gcsize * sizeof(eword_t));
    }
}

enamespace_t *
enew_namespace(esymbol_t *name, enamespace_t *parent)
{
    enamespace_t	*namespace;

    if (namespace_vector->offset >= namespace_vector->length)
	erenew_vector(namespace_vector, namespace_vector->length + 8);
    enew_object(namespace_vector->v.ptr + namespace_vector->offset,
		t_namespace, sizeof(enamespace_t));
    namespace = namespace_vector->v.ptr[namespace_vector->offset];
    ++namespace_vector->offset;

    namespace->name = name;
    enew_hash((eobject_t *)&namespace->fields, 8);
    namespace->parent = parent;

    enew_vector((eobject_t *)&namespace->vector, t_void, 4);

    return (namespace);
}
